<?php
/**
 * FastApp.
 * 8/14/24
 * @Author 深色的海 (wx:deepsea_0024)
 * @Contact telegram:deepsea159
 */

namespace App\Fastapp\Api\Service\Account;

use App\Fastapp\Exception\TipException;
use App\Fastapp\Listener\Event\AccountRechargeEvent;
use App\Fastapp\Listener\Event\AccountWalletExpendEvent;
use App\Fastapp\Listener\Event\AccountWithdrawalEvent;
use App\Fastapp\Model\AccountMessage;
use App\Fastapp\Model\AccountRecharge;
use App\Fastapp\Model\AccountWallet;
use App\Fastapp\Model\AccountWalletLog;
use App\Fastapp\Model\AccountWithdrawal;
use App\Fastapp\Model\Currency;
use Hyperf\DbConnection\Db;
use Mine\Helper\Str;

class WalletService
{
    public static function recharge(AccountRecharge $md): bool
    {
        Db::beginTransaction();
        try {
            $md->update(['status' => 1, 'pay_at' => date('Y-m-d H:i:s')]);
            $wallet = AccountWallet::query()->with(['currency:id,trade_precision'])->where(['user_id' => $md->user_id, 'symbol' => $md->symbol])->sharedLock()->first();
            $amount = $md->amount;
            if ($md->pump) $amount = bcsub($amount, $md->pump);
            AccountWallet::query()->where(['id' => $wallet->id])->increment('balance', $amount);
            AccountWalletLog::query()->create([
                'user_id' => $md->user_id,
                'wallet_id' => $wallet->id,
                'currency_id' => $wallet->currency_id,
                'symbol' => $wallet->symbol,
                'type' => AccountWalletLog::RECHARGE,
                'object_id' => $md->id,
                'object_type' => AccountWalletLog::OBJ_TYPE_RECHARGE,
                'amount' => $md->amount,
                'field' => 'balance',
                'before' => $wallet->balance,
                'after' => bcadd($wallet->balance, $md->amount, $wallet->currency->trade_precision),
            ]);
            AccountMessage::query()->create([
                'user_id' => $md->user_id,
                'object_type' => AccountWalletLog::OBJ_TYPE_RECHARGE,
                'object_id' => $md->id,
                'title' => fastapp_t('pay.recharge_message_title'),
                'content' => fastapp_t('pay.recharge_message_content', ['amount' => $md->amount, 'symbol' => $md->symbol])
            ]);
            Db::commit();
            api_dispatch(new AccountRechargeEvent($md->user_id, $md->id, get_user_cache('type', $md->user_id)));
            return true;
        } catch (\Throwable $throwable) {
            console()->error($throwable->getMessage());
            Db::rollBack();
        }
        return false;
    }

    public static function withdrawal(array $data): bool
    {
        Db::beginTransaction();
        try {
            $wallet = AccountWallet::query()
                ->with(['currency:id,trade_precision'])
                ->where(['user_id' => $data['user_id'], 'symbol' => $data['symbol']])
                ->where('balance', '>=', $data['amount'])
                ->sharedLock()->first();
            if (!$wallet) throw new TipException(fastapp_t('pay.insufficient_funds'));
            if (!AccountWallet::query()->where(['id' => $wallet->id])->where('balance', '>=', $data['amount'])->decrement('balance', $data['amount'])) throw new TipException(fastapp_t('pay.insufficient_funds'));
            $setting = api_cache()->getAppSetting(get_user_cache('type', $wallet->user_id));
            if (!empty($setting['pump_withdrawal'])) $data['pump'] = bcmul($data['amount'], bcdiv($setting['pump_withdrawal'], '100', 8), 4);
            $md = new AccountWithdrawal();
            $md->fill(array_merge($data, [
                'currency_id' => $wallet->currency_id,
                'wallet_id' => $wallet->id,
            ]));
            $md->save();
            AccountWalletLog::query()->create([
                'user_id' => $wallet->user_id,
                'wallet_id' => $wallet->id,
                'currency_id' => $wallet->currency_id,
                'symbol' => $wallet->symbol,
                'type' => AccountWalletLog::WITHDRAWAL,
                'object_type' => AccountWalletLog::OBJ_TYPE_WITHDRAWAL,
                'object_id' => $md->id,
                'field' => 'balance',
                'amount' => $md->amount,
                'before' => $wallet->balance,
                'after' => bcsub($wallet->balance, $data['amount'], $wallet->currency->trade_precision),
            ]);
            Db::commit();
            api_dispatch(new AccountWithdrawalEvent($wallet->user_id, $wallet->id, get_user_cache('type', $wallet->user_id)));
            return true;
        } catch (\Throwable $throwable) {
            console()->error($throwable->getMessage());
            Db::rollBack();
            if ($throwable instanceof TipException) throw new TipException($throwable->getMessage());
        }
        return false;
    }

    /**
     * 提现打款
     * @param int $id
     * @return bool
     */
    public static function payOut(int $id): bool
    {
        $md = AccountWithdrawal::query()->where(['id' => $id, 'status' => 1])->first();
        if (!$md) return true;
        $cs = '\\App\\Fastapp\\Api\\Service\\Pay\\Platform\\' . Str::studly($md->code) . 'Service';
        $cs::payOut($md);
        return true;
    }

    public static function inc(array $data, ?\Closure $fun = null, $field = 'balance'): AccountWallet
    {
        if (is_null($fun) && empty($data['amount'])) throw new TipException(fastapp_t('pay.invalid_amount'));
        $map['user_id'] = empty($data['user_id']) ? get_uid() : $data['user_id'];
        $map['symbol'] = empty($data['symbol']) ? get_user_cache('symbol') : $data['symbol'];
        Db::beginTransaction();
        try {
            $wallet = AccountWallet::query()->where($map)
                ->with(['currency:id,trade_precision'])
                ->sharedLock()->first();
            if ($fun) {
                $fun($data, $wallet);
            } else {
                $before = $wallet->$field;
                $wallet->$field = bcadd($wallet->$field, (string)$data['amount'], $wallet->currency->trade_precision);
                $wallet->save();
                $log = AccountWalletLog::query()->create([
                    'user_id' => $wallet->user_id,
                    'wallet_id' => $wallet->id,
                    'currency_id' => $wallet->currency_id,
                    'symbol' => $wallet->symbol,
                    'type' => $data['type'],
                    'object_type' => $data['object_type'],
                    'object_id' => $data['object_id'],
                    'field' => $field,
                    'amount' => $data['amount'],
                    'before' => $before,
                    'after' => $wallet->$field,
                ]);
                $wallet->log_id = $log->id;
                MessageService::add([
                    'user_id' => $wallet->user_id,
                    'title' => fastapp_t('fastapp.recharge', [], get_user_cache('lang', $wallet->user_id)),
                    'content' => fastapp_t('fastapp.recharge_to_account', ['amount' => $log->amount . ' ' . $wallet->symbol], get_user_cache('lang', $wallet->user_id)),
                    'object_id' => $log->id,
                    'object_type' => AccountWalletLog::RECHARGE
                ]);
            }
            Db::commit();
            return $wallet;
        } catch (\Throwable $throwable) {
            Db::rollBack();
            sys_notify('充值失败' . json_encode($data));
            throw new \Exception($throwable->getMessage());
        }
    }

    public static function dec(array $data, ?\Closure $fun = null, $field = 'balance'): AccountWallet
    {
        if (is_null($fun) && empty($data['amount'])) throw new TipException(fastapp_t('pay.invalid_amount'));
        $map['user_id'] = empty($data['user_id']) ? get_uid() : $data['user_id'];
        $map['symbol'] = empty($data['symbol']) ? get_user_cache('symbol') : $data['symbol'];
        $query = AccountWallet::query()->where($map)
            ->with(['currency:id,trade_precision'])
            ->sharedLock();
        Db::beginTransaction();
        try {
            if ($fun) {
                $wallet = $fun($data, $query);
            } else {
                $wallet = $query->where($field, '>=', $data['amount'])->first();
                if (!$wallet) throw new TipException(fastapp_t('pay.insufficient_funds'));
                $before = $wallet->$field;
                $wallet->$field = bcsub($wallet->$field, (string)$data['amount'], $wallet->currency->trade_precision);
                $wallet->save();
                $log = AccountWalletLog::query()->create([
                    'user_id' => $wallet->user_id,
                    'wallet_id' => $wallet->id,
                    'currency_id' => $wallet->currency_id,
                    'symbol' => $wallet->symbol,
                    'type' => $data['type'],
                    'object_type' => $data['object_type'],
                    'object_id' => $data['object_id'],
                    'field' => $field,
                    'amount' => $data['amount'],
                    'before' => $before,
                    'after' => $wallet->$field,
                ]);
                $wallet->log_id = $log->id;
            }
            Db::commit();
            api_dispatch(new AccountWalletExpendEvent($wallet->user_id, $wallet->log_id, get_user_cache('type', $wallet->user_id)));
            return $wallet;
        } catch (\Throwable $throwable) {
            Db::rollBack();
            if ($throwable instanceof TipException) throw new TipException($throwable->getMessage());
        }
        throw new \Exception('Server Error');
    }

    public static function balanceTransfer(string $symbol, string $amount, string $account): AccountWallet
    {
        $map['user_id'] = get_uid();
        $map['symbol'] = $symbol;
        Db::beginTransaction();
        try {
            $wallet = AccountWallet::query()->where($map)->sharedLock()->firstOrFail();
            if (bccomp($wallet->balance, $amount) == -1) throw new TipException(fastapp_t('account.balance_not_enough'));
            $wallet->balance = bcsub($wallet->balance, $amount, 8);
            $wallet->$account = bcadd($wallet->$account, $amount, 8);
            $wallet->save();
            Db::commit();
            return $wallet;
        } catch (\Throwable $throwable) {
            Db::rollBack();
            if ($throwable instanceof TipException) throw $throwable;
            sys_notify('划转: ' . $throwable->getMessage());
        }
        throw new TipException(fastapp_t('fastapp.invalid_operation'));
    }

    public function list(array $param): array
    {
        $map['user_id'] = get_uid();
        $query = AccountWallet::query()
            ->select(['id', 'currency_id', 'symbol', 'balance', 'address']);
        if (!empty($param['type'])) {
            $ids = Currency::query()->where(['type' => $param['type']])->pluck('id');
            $query->whereIn('currency_id', $ids);
        }
        $res = $query->where($map)->get()->mapWithKeys(function ($item) {
            return [$item['symbol'] => $item];
        })->toArray();
        $list = [];
        $currency = get_currency_cache();
        foreach (explode(',', api_cache()->getAppSetting(fastapp_header('account-type', 1))['symbol']) as $symbol) {
            $list[$symbol] = $res[$symbol];
            $list[$symbol]['currency'] = $currency[$symbol];
        }
        return $list;
    }

    public function detail(string $symbol): array
    {
        $map['user_id'] = get_uid();
        $map['symbol'] = $symbol;
        $res = AccountWallet::query()->where($map)->firstOrFail()->toArray();
        $res['currency'] = get_currency_cache($symbol);
        return $res;
    }

    public function fundsTransfer(array $data): array
    {
        return self::balanceTransfer($data['symbol'], $data['amount'], $data['account'])->toArray();
    }

    public function accountTransfer(array $data): array
    {
        $uid = get_uid();
        $ojb = [
            'user_id' => $uid,
            'amount' => $data['amount'],
            'symbol' => $data['symbol'],
            'type' => AccountWalletLog::USE_BALANCE,
            'object_type' => AccountWalletLog::OBJ_TYPE_WALLET_TRANSFER,
            'object_id' => 0
        ];
        self::dec($ojb);
        $ojb['type'] = AccountWalletLog::RECHARGE;
        $ojb['user_id'] = $data['to_user_id'];
        self::inc($ojb);
        return ['status' => 1];
    }
}